package gui.inventory;

import gui.common.Controller;
import gui.item.ItemData;
import gui.product.ProductData;

import java.util.Collection;
import java.util.Iterator;

import model.entity.Item;
import model.entity.Product;
import model.entity.container.ProductContainer;
import model.entity.container.ProductGroup;
import model.entity.container.StorageUnit;
import model.events.*;
import model.management.ItemManager;
import model.management.ProductGroupManager;
import model.management.ProductManager;
import model.management.StorageUnitManager;
import productidentification.ProductIdentifierScanner;

import common.Result;
import common.Size;
import common.util.Tools;

/**
 * Controller class for inventory view.
 */
public class InventoryController extends Controller implements IInventoryController
{
  private ProductContainerData root;

  /**
   * Constructor.
   * 
   * @param view
   *          Reference to the inventory view
   */
  public InventoryController(IInventoryView view)
  {
    super(view);

    construct();

    EntityListener listener = generateItemListener();
    ItemManager.getInstance().addEntityEventListener(listener);

    listener = generateProductListener();
    ProductManager.getInstance().addEntityEventListener(listener);

    listener = generateStorageUnitListener();
    StorageUnitManager.getInstance().addEntityEventListener(listener);

    listener = generateProductGroupListener();
    ProductGroupManager.getInstance().addEntityEventListener(listener);

    new ProductIdentifierScanner().scan();
  }

  /**
   * Returns a reference to the view for this controller.
   */
  @Override
  protected IInventoryView getView()
  {
    return (IInventoryView) super.getView();
  }

  /**
   * Loads data into the controller's view.
   * 
   * {@pre None}
   * 
   * {@post The controller has loaded data into its view}
   */
  @Override
  protected void loadValues()
  {
    buildProductContainerTree();

    getView().setProductContainers(root);
  }

  /**
   * Sets the enable/disable state of all components in the controller's view. A component should
   * be enabled only if the user is currently allowed to interact with that component.
   * 
   * {@pre None}
   * 
   * {@post The enable/disable state of all components in the controller's view have been set
   * appropriately.}
   */
  @Override
  protected void enableComponents()
  {
    return;
  }

  //
  // IInventoryController overrides
  //

  /**
   * Returns true if and only if the "Add Storage Unit" menu item should be enabled.
   */
  @Override
  public boolean canAddStorageUnit()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Add Items" menu item should be enabled.
   */
  @Override
  public boolean canAddItems()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Transfer Items" menu item should be enabled.
   */
  @Override
  public boolean canTransferItems()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Remove Items" menu item should be enabled.
   */
  @Override
  public boolean canRemoveItems()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Delete Storage Unit" menu item should be enabled.
   */
  @Override
  public boolean canDeleteStorageUnit()
  {
    StorageUnit unit = (StorageUnit) getView().getSelectedProductContainer().getTag();
    Result result = StorageUnitManager.validateRemove(unit);
    return result.getStatus();
  }

  /**
   * This method is called when the user selects the "Delete Storage Unit" menu item.
   */
  @Override
  public void deleteStorageUnit()
  {
    StorageUnit unit = (StorageUnit) getView().getSelectedProductContainer().getTag();
    Result result = StorageUnitManager.remove(unit);

    if (result.getStatus() == false)
      getView().displayErrorMessage(
          "Removing the selected Storage Unit failed for the following reason:\n"
              + result.getMessage());

    StorageUnitManager.save();
  }

  /**
   * Returns true if and only if the "Edit Storage Unit" menu item should be enabled.
   */
  @Override
  public boolean canEditStorageUnit()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Add Product Group" menu item should be enabled.
   */
  @Override
  public boolean canAddProductGroup()
  {
    return true;
  }

  /**
   * Returns true if and only if the "Delete Product Group" menu item should be enabled.
   */
  @Override
  public boolean canDeleteProductGroup()
  {
    ProductGroup productGroup = (ProductGroup) getView().getSelectedProductContainer().getTag();
    Result canDelete = ProductGroupManager.validateRemoveProductGroup(productGroup);
    return canDelete.getStatus();
  }

  /**
   * Returns true if and only if the "Edit Product Group" menu item should be enabled.
   */
  @Override
  public boolean canEditProductGroup()
  {
    return true;
  }

  /**
   * This method is called when the user selects the "Delete Product Group" menu item.
   */
  @Override
  public void deleteProductGroup()
  {
    ProductGroup productGroup = (ProductGroup) getView().getSelectedProductContainer().getTag();
    ProductGroupManager.removeProductGroup(productGroup);
  }

  /**
   * This method is called when the selected product container changes.
   */
  @Override
  public void productContainerSelectionChanged()
  {
    ProductContainerData data = getView().getSelectedProductContainer();
    updateContainer(data.getTag());
  }

  /**
   * This method is called when the selected product changes.
   */
  @Override
  public void productSelectionChanged()
  {
    ProductContainer container = getView().getSelectedProductContainer().getTag();
    ProductData data = getView().getSelectedProduct();
    updateItems(container, data.getTag());
    getView().selectProduct(data);
  }

  /**
   * This method is called when the selected item changes.
   */
  @Override
  public void itemSelectionChanged()
  {
    return;
  }

  /**
   * Returns true if and only if the "Delete Product" menu item should be enabled.
   */
  @Override
  public boolean canDeleteProduct()
  {
    Product product = getView().getSelectedProduct().getTag();
    ProductContainer pc = getView().getSelectedProductContainer().getTag();
    Result canDelete = null;
    if(pc != null){
    	canDelete = ProductManager.validateRemoveProductFromContainer(product, pc);
    }
    else{
    	canDelete = ProductManager.validateRemoveProduct(product);
    }
    return canDelete.getStatus();
  }

  /**
   * This method is called when the user selects the "Delete Product" menu item.
   */
  @Override
  public void deleteProduct()
  {
    Product product = getView().getSelectedProduct().getTag();
    ProductContainer pc = getView().getSelectedProductContainer().getTag();
    if(pc != null){
        ProductManager.removeProductFromContainer(product, pc);
    }
    else{
    	ProductManager.removeProduct(product);
    }
  }

  /**
   * Returns true if and only if the "Edit Item" menu item should be enabled.
   */
  @Override
  public boolean canEditItem()
  {
    return true;
  }

  /**
   * This method is called when the user selects the "Edit Item" menu item.
   */
  @Override
  public void editItem()
  {
    getView().displayEditItemView();
  }

  /**
   * Returns true if and only if the "Remove Item" menu item should be enabled.
   */
  @Override
  public boolean canRemoveItem()
  {
    return true;
  }

  /**
   * This method is called when the user selects the "Remove Item" menu item.
   */
  @Override
  public void removeItem()
  {
    ItemData idata = getView().getSelectedItem();
    Item item = idata.getTag();
    Result result = ItemManager.removeItem(item);

    if (result.getStatus() == false)
      getView().displayErrorMessage("Remove item failed!\n\nMessage: " + result.getMessage());
  }

  /**
   * Returns true if and only if the "Edit Product" menu item should be enabled.
   */
  @Override
  public boolean canEditProduct()
  {
    return true;
  }

  /**
   * This method is called when the user selects the "Add Product Group" menu item.
   */
  @Override
  public void addProductGroup()
  {
    getView().displayAddProductGroupView();
  }

  /**
   * This method is called when the user selects the "Add Items" menu item.
   */
  @Override
  public void addItems()
  {
    getView().displayAddItemBatchView();
  }

  /**
   * This method is called when the user selects the "Transfer Items" menu item.
   */
  @Override
  public void transferItems()
  {
    getView().displayTransferItemBatchView();
  }

  /**
   * This method is called when the user selects the "Remove Items" menu item.
   */
  @Override
  public void removeItems()
  {
    getView().displayRemoveItemBatchView();
  }

  /**
   * This method is called when the user selects the "Add Storage Unit" menu item.
   */
  @Override
  public void addStorageUnit()
  {
    getView().displayAddStorageUnitView();
  }

  /**
   * This method is called when the user selects the "Edit Product Group" menu item.
   */
  @Override
  public void editProductGroup()
  {
    getView().displayEditProductGroupView();
  }

  /**
   * This method is called when the user selects the "Edit Storage Unit" menu item.
   */
  @Override
  public void editStorageUnit()
  {
    getView().displayEditStorageUnitView();
  }

  /**
   * This method is called when the user selects the "Edit Product" menu item.
   */
  @Override
  public void editProduct()
  {
    getView().displayEditProductView();
  }

  /**
   * This method is called when the user drags a product into a product container.
   * 
   * @param productData
   *          Product dragged into the target product container
   * @param containerData
   *          Target product container
   */
  @Override
  public void addProductToContainer(ProductData productData, ProductContainerData containerData)
  {
    Result result = ProductManager.addProductToContainer(containerData.getTag(),
        productData.getTag());

    if (result.getStatus() == false)
      getView().displayErrorMessage("Failed to move product!\n" + result.getMessage());
    else
      updateProducts(getView().getSelectedProductContainer().getTag());
  }

  /**
   * This method is called when the user drags an item into a product container.
   * 
   * @param item
   *          Item dragged into the target product container
   * @param container
   *          Target product container
   */
  @Override
  public void moveItemToContainer(ItemData item, ProductContainerData container)
  {
    ItemManager.moveItemToProductContainer(item.getTag(), container.getTag());
  }

  private void updateEntireDisplay()
  {
    updateProductContainers();
    ProductContainerData data = getView().getSelectedProductContainer();
    if (data == null)
      clearView();
    else if (data.equals(root))
      setRootContainer();
    else
      updateContainer(data.getTag());
  }

  private void updateProductContainers()
  {
    ProductContainerData selected = getView().getSelectedProductContainer();

    buildProductContainerTree();
    getView().setProductContainers(root);

    if (productContainerTreeContains(root, selected))
      getView().selectProductContainer(selected);
    else
      clearView();
  }

  private void buildProductContainerTree()
  {
    root = new ProductContainerData();

    Iterator<StorageUnit> storageUnits = StorageUnitManager.getStorageUnits();
    while (storageUnits.hasNext())
    {
      ProductContainer unit = storageUnits.next();
      ProductContainerData data = unit.generateProductContainerData();
      addProductContainerChildren(unit, data);

      root.addChild(data);
    }
  }

  private void addProductContainerChildren(ProductContainer parent, ProductContainerData pdata)
  {
    Iterator<ProductGroup> groups = parent.getProductGroups();
    while (groups.hasNext())
    {
      ProductGroup group = groups.next();
      ProductContainerData data = group.generateProductContainerData();
      addProductContainerChildren(group, data);
      pdata.addChild(data);
    }
  }

  private boolean productContainerTreeContains(ProductContainerData root,
      ProductContainerData query)
  {
    for (int i = 0; i < root.getChildCount(); i++)
    {
      if (root.getChild(i).equals(query))
        return true;
      if (productContainerTreeContains(root.getChild(i), query))
        return true;
    }

    return false;
  }

  private void clearView()
  {
    getView().setProducts(new ProductData[0]);
    getView().setItems(new ItemData[0]);
    getView().setContextUnit("");
    getView().setContextGroup("");
    getView().setContextSupply("");
  }

  private void setRootContainer()
  {
    setRootContextFields();
    updateProducts(null);
    getView().selectProductContainer(root);
  }

  private void setRootContextFields()
  {
    Tools.printContainerTree();
    getView().setContextUnit("All");
    getView().setContextGroup("");
    getView().setContextSupply("");
  }

  private void updateContainer(ProductContainer container)
  {
    setContextFields(container);
    updateProducts(container);
  }

  private void setContextFields(ProductContainer container)
  {
    if (container == null)
    {
      setRootContextFields();
      return;
    }

    getView().setContextUnit(container.getStorageUnit().getName());

    if (container instanceof ProductGroup)
    {
      getView().setContextGroup(container.getName());
      Size supply = ((ProductGroup) container).getThreeMonthSupply();
      if (supply.getAmount() > 0)
        getView().setContextSupply(supply.toString());
      else
        getView().setContextSupply("");
    }
    else
    {
      getView().setContextGroup("");
      getView().setContextSupply("");
    }
  }

  /**
   * Updates the products section of the display
   * 
   * @param container
   *          The container whose products are to be displayed, or <code>null</code> if all
   *          products are to be displayed.
   */
  private void updateProducts(ProductContainer container)
  {
    ProductData selected = getView().getSelectedProduct();

    Collection<ProductData> products = ProductManager.getProductData(container);
    ProductData[] pdata = products.toArray(new ProductData[products.size()]);

    getView().setProducts(pdata);

    if (container == null && selected != null)
      updateItems(null, selected.getTag());
    else if ((selected = getEqualObjectInArray(pdata, selected)) != null)
    {
      getView().selectProduct(selected);
      updateItems(container, selected.getTag());
    }
    else
      getView().setItems(new ItemData[0]);
  }

  /**
   * updates the item section of the display
   * 
   * @param container
   *          The container whose items of the given product are to be displayed, or
   *          <code>null</code> if all items in the system for the given product are to be
   *          displayed.
   * @param product
   *          The product whose items are to be displayed
   */
  private void updateItems(ProductContainer container, Product product)
  {
    ItemData selected = getView().getSelectedItem();

    Collection<ItemData> items = ItemManager.getItemData(container, product);
    ItemData[] itemData = items.toArray(new ItemData[items.size()]);

    getView().setItems(itemData);

    if ((selected = getEqualObjectInArray(itemData, selected)) != null)
      getView().selectItem(selected);
  }

  private void select(ProductContainer container)
  {
    ProductContainerData data = container.generateProductContainerData();
    getView().selectProductContainer(data);
    setContextFields(container);
    updateProducts(container);
  }

  private void select(ProductData data, ProductContainer container)
  {
    if (data != null)
    {
      getView().selectProduct(data);
      updateItems(container, data.getTag());
    }
  }

  private <T> T getEqualObjectInArray(T[] array, T value)
  {
    for (int i = 0; i < array.length; i++)
    {
      if (array[i].equals(value))
        return array[i];
    }

    return null;
  }

  private EntityListener generateStorageUnitListener()
  {
    return new EntityEventAdapter()
    {
      @Override
      public void entityAdded(EntityEvent event)
      {
        updateEntireDisplay();

        select((StorageUnit) event.getEntity());
      }

      @Override
      public void entityRemoved(EntityEvent event)
      {
        updateEntireDisplay();
      }

      @Override
      public void entityModified(EntityModifiedEvent event)
      {
        ProductData data = getView().getSelectedProduct();
        updateEntireDisplay();

        StorageUnit unit = (StorageUnit) event.getNewData();
        select(unit);
        select(data, unit);
      }
    };
  }

  private EntityListener generateProductGroupListener()
  {
    return new EntityListener()
    {
      @Override
      public void entityAdded(EntityEvent event)
      {
        updateEntireDisplay();

        select((ProductGroup) event.getEntity());
      }

      @Override
      public void entityRemoved(EntityEvent event)
      {
        updateEntireDisplay();
      }

      @Override
      public void entityMoved(EntityEvent event)
      {
        updateEntireDisplay();

        select((ProductGroup) event.getEntity());
      }

      @Override
      public void entityModified(EntityModifiedEvent event)
      {
        updateEntireDisplay();

        select((ProductGroup) event.getNewData());
      }
    };
  }

  private EntityListener generateProductListener()
  {
    return new EntityListener()
    {
      @Override
      public void entityMoved(EntityEvent event)
      {
        if (inventoryEventRequiresUpdate(event))
          handleInventoryEvent(event);
      }

      @Override
      public void entityAdded(EntityEvent event)
      {
        if (inventoryEventRequiresUpdate(event))
          handleInventoryEvent(event);
      }

      @Override
      public void entityRemoved(EntityEvent event)
      {
        if (inventoryEventRequiresUpdate(event))
          handleInventoryEvent(event);
      }

      private boolean inventoryEventRequiresUpdate(EntityEvent event)
      {
        ProductContainerData selected = getView().getSelectedProductContainer();

        ProductContainer container = event.getNewContainer();
        if (container != null && container.equals(selected))
          return true;

        container = event.getOldContainer();
        if (container != null && container.equals(selected))
          return true;

        return false;
      }

      private void handleInventoryEvent(EntityEvent event)
      {
        ProductContainer container = event.getNewContainer();
        updateProducts(container);
      }

      @Override
      public void entityModified(EntityModifiedEvent event)
      {
        if (modifiedEventRequiresUpdate(event))
          productSelectionChanged();
          ProductContainer container = getView().getSelectedProductContainer().getTag();
          updateProducts(container);
      }

      private boolean modifiedEventRequiresUpdate(EntityModifiedEvent event)
      {
        ProductData selected = getView().getSelectedProduct();
        Product product = (Product) event.getNewData();
        if (product.equals(selected).getStatus())
          return true;

        product = (Product) event.getOldData();
        if (product.equals(selected).getStatus())
          return true;

        return false;
      }
    };
  }

  private EntityListener generateItemListener()
  {
    return new EntityEventAdapter()
    {
      @Override
      public void entityMoved(EntityEvent event)
      {
        updateItems(getItem(event));
      }

      @Override
      public void entityAdded(EntityEvent event)
      {
        updateItems(getItem(event));
      }

      @Override
      public void entityRemoved(EntityEvent event)
      {
        updateItems(getItem(event));
      }

      @Override
      public void entityModified(EntityModifiedEvent event)
      {
        updateItems(getItem(event));
      }

      private Item getItem(GenericEntityEvent event)
      {
        if (event instanceof EntityEvent)
          return (Item) ((EntityEvent) event).getEntity();
        else
          return (Item) ((EntityModifiedEvent) event).getOldData();
      }

      /**
       * Updates the display of items
       * 
       * @param item
       */
      private void updateItems(Item item)
      {
        ProductContainer container = item.getContainer();
        select(container);
        updateProducts(container);
      }
    };
  }
}
